package deployment

import (
	"fmt"

	"github.com/buger/jsonparser"
	"github.com/pingcap/errors"
	"github.com/romberli/das/config"
	"github.com/romberli/das/global"
	"github.com/romberli/das/internal/dependency/deployment"
	"github.com/romberli/das/internal/dependency/metadata"
	"github.com/romberli/go-util/constant"
	"github.com/romberli/go-util/middleware"
	"github.com/spf13/viper"
)

const (
	tokenJSON = "token"
	codeJSON  = "code"

	defaultEmptyDeploymentInfo = ".."
)

var _ deployment.Service = (*Service)(nil)

type Service struct {
	Repository  deployment.Repository
	MySQLServer metadata.MySQLServer
}

// NewService returns Service with given middleware.Pool
func NewService(db middleware.Pool, mysqlServer metadata.MySQLServer) deployment.Service {
	return newService(db, mysqlServer)
}

func NewServiceWithDefault(mysqlServer metadata.MySQLServer) deployment.Service {
	return newService(global.DASMySQLPool, mysqlServer)
}

// newService returns *Service with given middleware.Pool
func newService(db middleware.Pool, mysqlServer metadata.MySQLServer) *Service {
	return &Service{
		Repository:  NewRepository(db),
		MySQLServer: mysqlServer,
	}
}

// GetMySQLServer returns the MySQLServer
func (s *Service) GetMySQLServer() metadata.MySQLServer {
	return s.MySQLServer
}

// GetDASServerAddr gets the das server host info with given deployment region, deployment org and network zone
func (s *Service) GetDASServerAddr() (string, error) {
	return s.Repository.GetServerHostInfo(s.GetMySQLServer().GetDeploymentRegion(), s.GetMySQLServer().GetDeploymentOrg(), s.GetMySQLServer().GetNetworkZone())
}

// CheckAccessibility checks if the das server is accessible to the mysql server
func (s *Service) CheckAccessibility() bool {
	accessibleZones := viper.GetStringSlice(config.ServerDeploymentAccessibleZonesKey)
	if len(accessibleZones) == constant.ZeroInt {
		return true
	}

	deploymentInfo := fmt.Sprintf("%s.%s.%s", s.GetMySQLServer().GetDeploymentRegion(), s.GetMySQLServer().GetDeploymentOrg(), s.GetMySQLServer().GetNetworkZone())
	if deploymentInfo == defaultEmptyDeploymentInfo {
		return true
	}

	for _, zone := range accessibleZones {
		if zone == deploymentInfo {
			return true
		}
	}

	return false
}

// PostDAS calls the das server, agent server should not call this method
func (s *Service) PostDAS(path string, data []byte) ([]byte, error) {
	if viper.GetString(config.RoleKey) == config.AgentRole {
		return nil, errors.Errorf("deployment Service.PostDAS(): agent role should not call this method")
	}

	serverAddr, err := s.GetDASServerAddr()
	if err != nil {
		return nil, err
	}
	// get special token
	token, err := s.getSpecialToken()
	if err != nil {
		return nil, err
	}
	// set token to request body
	data, err = jsonparser.Set(data, []byte(fmt.Sprintf(`"%s"`, token)), tokenJSON)
	if err != nil {
		return nil, err
	}
	// call remote das server
	resp, err := global.HTTPClient.PostDAS(serverAddr+path, data)
	if err != nil {
		return nil, err
	}

	return jsonparser.Delete(resp, codeJSON), nil
}

func (s *Service) getSpecialToken() (string, error) {
	tokens := viper.GetStringSlice(config.PrivilegeSpecialTokensKey)
	if len(tokens) == constant.ZeroInt {
		return constant.EmptyString, errors.New("deployment Service.getSpecialToken(): no special token found")
	}

	return tokens[constant.ZeroInt], nil
}
